﻿using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RainEngine.SceneNodes;


namespace RainEngine.SceneManagement
{
    /// <summary>
    /// 处理拾取的辅助类
    /// </summary>
    public class Pickor
    {
        // 对引擎的引用
        private RainGE  engine;
        
        // 用于碰撞检测的射线
        private Ray     pickingRay;        

        // 对场景管理器的引用
        internal static SceneManager scm;

        private Pickor() { }

        /// <summary>
        /// 创建一个Pickor对象
        /// </summary>
        /// <param name="engine">引擎</param>
        /// <param name="scm">场景管理器</param>
        internal Pickor(RainGE engine,SceneManager scm)
        {
            this.engine = engine;
            Pickor.scm = scm ;
            pickingRay = new Ray();
        }

        #region 包围盒拾取
        
        /// <summary>
        /// 根据给定的屏幕坐标返回被拾取的SceneNode，如果有多个节点，则返回离开相机最近的一个。
        /// </summary>
        /// <param name="x">屏幕X坐标</param>
        /// <param name="y">屏幕Y坐标</param>
        /// <returns></returns>
        public SceneNode PickBB(int x, int y)
        {
            // 获取与射线相交的场景节点集合
            List<PickedNode> pickedNodes = GetRayIntersections(CreateRayFromScreen(x,y));

            // 如果集合大小为0，则返回null
            if (pickedNodes.Count == 0)
                return null;
            
            // 如果集合大小为1，则返回第一个节点
            else if (pickedNodes.Count == 1)
                return pickedNodes[0].Node;
            else
            {
                float minDistance = float.MaxValue;
                SceneNode resultNode = null;
                // 遍历集合中的所有节点找到离相机最近的那一个节点
                foreach (PickedNode pn in pickedNodes)
                {
                    if (pn.DistanceFromCamera < minDistance)
                    {
                        minDistance = pn.DistanceFromCamera;
                        resultNode = pn.Node;
                    }
                }
                return resultNode;
            }
        }
        /// <summary>
        /// 根据给定坐标创建一条从近裁平面指向远裁平面的射线
        /// </summary>
        /// <param name="x">屏幕x坐标</param>
        /// <param name="y">屏幕y坐标</param>
        /// <returns></returns>
        public Ray CreateRayFromScreen(int x, int y)
        {            
            // 获取相机投影矩阵和视矩阵
            CameraSceneNode camera = scm.ActiveScene.Camera;
            Matrix view, projection;
            camera.GetViewProjection(out view, out projection);
            Viewport vp = engine.GraphicsDevice.Viewport;
            
            // 根据光标位置创建两个位于屏幕坐标中的位置，0表示距离相机尽量近，1表示距离相机尽量远。
            // 然后使用Viewport.Unproject获取这两个屏幕空间中的位置在世界空间中的位置。
            Vector3 rayStart = vp.Unproject(new Vector3(x, y, 0f), projection, view, Matrix.Identity);
            Vector3 rayEnd = vp.Unproject(new Vector3(x, y, 1f), projection, view, Matrix.Identity);

            // 根据上面计算得出的两个位置获取从rayStart指向rayEnd的向量并进行归一化，然后返回这条射线
            pickingRay = new Ray(rayStart, Vector3.Normalize(rayEnd - rayStart));
            return pickingRay;
        }

        /// <summary>
        /// 获取所有与射线相交的节点的集合，这些节点被包装在一个PickedNode对象中。
        /// </summary>
        /// <param name="ray">射线</param>
        /// <param name="checkAll">如果设为true，则检测所有场景节点（包括不可见节点），否则只检测可见的场景节点</param>
        /// <returns>相交节点的集合</returns>
        public List<PickedNode> GetRayBBIntersections(Ray ray, bool checkAll)
        {
            List<PickedNode> pickedNodes = new List<PickedNode>(20);
            foreach (Renderable3DSceneNode node in scm.ActiveScene.Nodes )
            {
                if (!checkAll && (node is Renderable3DSceneNode) && !((Renderable3DSceneNode)node).Visible)
                    continue;

                // 进行射线与场景节点的轴对齐包围盒相交检测
                if (ray.Intersects(node.Pose.aaBB) != null)
                {
                    PickedNode pn = new PickedNode(node);                    
                    pickedNodes.Add(pn);
                }
            }
            return pickedNodes;
        }

        /// <summary>
        /// 获取所有与射线相交的可见节点的集合，这些节点被包装在一个PickedNode对象中。
        /// </summary>
        /// <param name="ray">射线</param>
        /// <returns>相交节点的集合</returns>
        public List<PickedNode> GetRayIntersections(Ray ray) { return GetRayBBIntersections(ray, false); }
        
        #endregion        

        /// <summary>
        /// 将场景节点以及它离开相机的距离等信息包装起来的辅助类
        /// </summary>
        public struct PickedNode
        {
            /// <summary>
            /// 创建一个新PickedNode结构
            /// </summary>
            /// <param name="node"></param>            
            public PickedNode(Renderable3DSceneNode node)
            {
                Vector3 cameraPosition = Pickor.scm.ActiveScene.Camera.Pose.Position;
                this.Node = node;
                this.ContainsCamera = node.Pose.aaBB.Contains(cameraPosition) == ContainmentType.Contains;
                if (this.ContainsCamera)
                    this.DistanceFromCamera = 0;
                else
                    this.DistanceFromCamera = Vector3.Distance(cameraPosition, node.Pose.Position);
            }

            /// <summary>
            /// 被拾取的SceneNode
            /// </summary>
            public Renderable3DSceneNode Node;
            
            /// <summary>
            /// 被拾取的Scenenode离开相机的距离
            /// </summary>
            public float DistanceFromCamera;

            /// <summary>
            /// 如果相机包含在此节点的包围体中则返回true
            /// </summary>
            public bool ContainsCamera;
        }
    }
}
